UART2 auf dem STM8S105
Zur Verwendung der UART2 auf dem STM8S105 werden folgende Vorbereitungen getroffen um Daten übertragen zu können.
Im Segment `ram0` (der Bereich des SRAMs der durch ein Byte adressiert werden kann) werden folgende Variablen angelegt. TXCHAR und RXCHAR sind Zwischenspeicher für die aktuellen Zeichen die geschrieben oder gelesen werden sollen. Das Flag RXSTAT zeigt den Status des Empfangspuffers an (leer oder mindestens ein Zeichen wartet auf Verarbeitung).
;--------------------------------------------------------------------------------------
segment 'ram0'
;--------------------------------------------------------------------------------------
TXCHAR ds.b ; UART Transmit Character Buffer
RXCHAR ds.b ; UART Receive Character Buffer
RXSTAT ds.b ; UART Receive Buffer empty (= 0)
Im Segment `ram1` werden die Pointer angelegt die auf die beiden zirkularen 64 Byte Puffer für Senden (UTX) bzw. für Empfangen (URX) zeigen. Dabei gibt es jeweils einen Pointer der auf das nächste freie Byte (Write) und Einen der auf das nächste zu lesende Byte (Read) zeigt. Die Variable „UART_ERR“ ist ein Status-Byte das eventuelle Fehler anzeigt (Puffer Überlauf).
;--------------------------------------------------------------------------------------
segment'ram1'
;--------------------------------------------------------------------------------------
UTX_WRT ds.b ; WRITE pointer (Byte) to Transmit Buffer
UTX_RD ds.b ; READ pointer (Byte) from Transmit Buffer
URX_WRT ds.b ; WRITE pointer (Byte) to Receive Buffer
URX_RD ds.b ; READ pointer (Byte) from Receive Buffer
UART_ERR ds.b ; UART Error register (Bit0 = Receive Buffer OVL)
; (Bit1 = Transmit Buffer OVL)
Der eigentliche Sendepuffer (UTX_BUF) wird durch die “segment 64” Direktive auf eine 64 Byte Grenze justiert was das Zeigerhandling vereinfacht und einen Pointerüberlauf vehindert. Der zirkulare Puffer wird im `ram1` Segment angelegt.
;--------------------------------------------------------------------------------------
segment 64 'ram1'
;--------------------------------------------------------------------------------------
UTX_BUF ds.b 64 ; UART Transmit Buffer 64 Byte / Modulo 64
Der Empfangspuffer (URX_BUF) wird auch durch die “segment 64” Direktive auf eine 64 Byte Grenze justiert was ebenfalls das Zeigerhandling vereinfacht und einen Pointerüberlauf vehindert. Der zirkulare Puffer wird im `ram1` Segment angelegt.
;--------------------------------------------------------------------------------------
segment 64 'ram1'
;--------------------------------------------------------------------------------------
URX_BUF ds.b 64 ; UART Receive Buffer 64 Byte / Modulo 64
Mit diesen Deklarationen sind die nötigen Variablen für den Betrieb der UART aufgesetzt so dass im nächsten Schritt die Initialisierung der UART Hardware erfolgen kann. Zu Beginn der Programmverarbeitung erhalten die diversen UART2 Register durch eine Reihe von Instruktionen die aus dem Code-Segment `rom` ausgeführt werden, ihre Werte. Die für die UART definierten Variablen müssen ebenfalls auf ihre Anfangswerte gesetzt werden.
;--------------------------------------------------------------------------------------
segment 'rom'
;--------------------------------------------------------------------------------------
|
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ld A,#$F1 ; |
Die beiden Register UART2_BRR1 und UART2_BRR2 setzen die Baud-Rate für die UART2. Der benutzte Wert bezieht sich auf eine STM8 Clock von 16 MHz. Die Reihenfolge der Registerzugriffe sollte eingehalten werden. Im UART2_CR1 (UART2 Configuration Register 1) wird Bit 6 gesetzt um eine 8-Bit Übertragung zu definieren.
Danach werden die Pointer für den Empfangs- und den Sende-Puffer initialisiert und das Empfangs-Status-Flag
gelöscht (kein Zeichen im Puffer).
Erst im Anschluss wird das UART2_CR3 (Configuration Register 3) auf 0x00h gestzt und damit die Übertragung mit einem STOP-Bit initialsiert.
Schliesslich werden in den Registern UART2_CR1 und UART2_CR2 die nötigen Bits gesetzt/rückgesetzt um die UART2 freizugeben und den Interrupt für empfangene Zeichen der UART2 zu aktivieren.
Schliesslich werden zur exakten Synchronisation die beiden (im weiteren Verlauf beschriebenen) Subroutinen
„TX_UART“ und „Read_Command“ aufgerufen. Diese werden zum aktuellen Zeitpunkt keine Charakter finden aber sicherstellen dass die beiden Ringpuffer im Zustand „leer“ sind.
Im weiteren Verlauf werden nun die Subroutinen beschrieben die von der eigentlichen Anwendung aufgerufen werden um Daten über die UART2 in beide Richtungen übertragen zu können. Diese Subroutinen befinden sich natürlich auch im Segment `rom` und stehen über ihre Einsprungadressen für die Applikation zur Verfügung.
Die Subroutine „Send_Char“ speichert das Zeichen das in der Variablen „TXCHAR“ bereitsteht im Ring-Puffer an der nächsten freien Stelle, insofern eine Stelle im Puffer frei ist! Wird keine freie Stelle gefunden so wird das Zeichen aus „TX_BUF“ verworfen und das Bit 1 in „UART_ERR“ gesetzt, welches einen Überlauf signalisiert.
|
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; This subroutine writes a character stored in TXCHAR (RAM) to the Transmit |
„Send-Char“ wird von der Applikation jedes mal aufgerufen nachdem die Anwendung ein Zeichen in der Variablen „TXCHAR“ zum Senden plaziert hat.
Äquivalent zum Senden eines Zeichens erfüllt die Subroutine „Read_Char“ die Aufgabe das nächste („älteste“) Zeichen im Empfangs-Puffer (Ringpuffer = FIFO) auszulesen und in der Variablen RXCHAR zur Verfügung zu stellen.
Das Pointer-Management stellt sicher dass der Lese-Pointer aktualisiert wird und das Status-Byte RXSTAT den aktuellen Zustand nach dem Auslesen wiedergibt (leer oder weitere Zeichen vorhanden).
|
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; This subroutine reads a character from the "Receive Buffer" and stores |
Die Subroutine “Read_Char” wird von der Anwendung aufgerufen, die die empfangenen Zeichen direkt weiterverarbeitet, um das nächste anstehende Zeichen aus dem Empfangspuffer auszulesen und entsprechend der Aufgabe des Programms zu verwenden.
Die beiden bisher besprochenen Subroutinen “Send_Char” und “Read_Char” stellen die Schnittstelle von der Applikation zu den zirkularen Puffern dar aber keines der Zeichen wurde dabei zwischen den Puffern und der UART2 Hardware übertragen.
Diese Aufgabe übernimmt in der Senderichtung die Subroutine „TX_UART“ die alle, aktuell im Sende-Puffer befindlichen Zeichen über die UART sendet.
„TX_UART“ ist eine Task die zyklisch (im verwendeten Beispiel alle 200 ms) aufgerufen wird um den Sendepuffer zu übertragen.
|
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TX_UART |
Das Senden von Zeichen wird zeitlich von der Software bestimmt und kann entsprechend kontrolliert werden. Die Task „TX_UART“ überträgt die im Sende-Puffer bereitstehenden Zeichen zu dem Zeitpunkt zu dem die Funktion aufgerufen wird. Geschieht das häufig genug besteht keine Gefahr dass Zeichen verloren gehen oder überschrieben werden.
Anders verhält sich das System in der Empfangsrichtung da die ankommenden Zeichen in der UART Hardware asynchron eingehen und mangels eines FIFOs sofort aus dem UART H/W Puffer ausgelesen werden müssen. Um zu vermeiden dass ein Zeichen durch überschreiben verloren geht muss das System eine kurze Task unmittelbar starten, die das empfange Zeichen aus dem UART H/W Puffer ausliest und in den UART-Empfangspuffer einträgt.
Wie in der Initialisierung beschrieben wird für die UART2 der „UART2 Receive Interrupt“ im Register UART2_CR2 freigeschaltet. D.h. durch jedes Zeichen das die UART2 empfängt wird der Interrupt ausgelöst und damit die nachfolgende „Interrupt Subroutine“ „UartRxInterrupt.l“ gestartet.
|
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ interrupt UartRx |
Schließlich und endlich muß die oben gelistete „Interrupt Service Routine“ hinsichtlich des Interrupt Vectors (Einsprung Adresse = UartRxInterrupt.l) in die Interrupt Vektor Tabelle eingetragen werden.
Jede Vektor-Adresse besteht aus 4 Byte daher wird der Adresswert in der Notation „xxxxxxxx.l“ angegeben (“l“ steht für „long“). Das „most significant Byte“ ist aber kein Teil der Adresse da der STM8 maximal 3 Byte lange Adressen unterstützt. Daher zeigen die verbleibenden 3 Byte des Interrupt Vektors auf den Startpunkt der Interrupt Service Routine.
Die Interrupt Vektoren sind in einem fest zugeordneten Adressbereich des Flash-Speichers, der durch die Angabe des Segments `vectit` dem Assembler mitgeteilt wird. Beim STM8S105 ist der erste Vektor auf der festen Adresse 0x008000h (Reset), alle weiteren Vektoren sind jeweils um weitere 4 Byte versetzt.
;--------------------------------------------------------------------------------------
segment 'vectit'
;--------------------------------------------------------------------------------------
Die Inhalte der Vektoren selbst stellen natürlich auch eine Adresse dar und berechnen sich aus der Summe des eingetragenen Wertes $8200.0000 + der Startadresse der Interrupt Service Routine. Da nur die geringer wertigen 3 Bytes ( = 0x00.0000h) als Adresse zählen entspricht die Vektor Adresse der Startadresse der Interrupt Service Routine allerdings wird das 4te Byte (0x82) hinzugezogen.
Diese Tabelle befindet sich am Ende des „main“ Source Codes und listet die verwendeten Interrupt Vektoren die im Programm benutzt werden:
|
;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dc.l {$82000000+main} ; reset dc.l {$82000000+NonHandledInterrupt} ; trap dc.l {$82000000+NonHandledInterrupt} ; irq0 TLI dc.l {$82000000+NonHandledInterrupt} ; irq1 AWU dc.l {$82000000+NonHandledInterrupt} ; irq2 CLK dc.l {$82000000+NonHandledInterrupt} ; irq3 Port A dc.l {$82000000+NonHandledInterrupt} ; irq4 Port B dc.l {$82000000+NonHandledInterrupt} ; irq5 Port C dc.l {$82000000+NonHandledInterrupt} ; irq6 Port D dc.l {$82000000+NonHandledInterrupt} ; irq7 Port E dc.l {$82000000+NonHandledInterrupt} ; Reserved dc.l {$82000000+NonHandledInterrupt} ; Reserved dc.l {$82000000+NonHandledInterrupt} ; irq10 SPI dc.l {$82000000+NonHandledInterrupt} ; irq11 TIM1 dc.l {$82000000+NonHandledInterrupt} ; irq12 TIM1 CapComp dc.l {$82000000+NonHandledInterrupt} ; irq13 TIM2 update dc.l {$82000000+NonHandledInterrupt} ; irq14 TIM2 CapComp dc.l {$82000000+NonHandledInterrupt} ; irq15 TIM3 update dc.l {$82000000+NonHandledInterrupt} ; irq16 TIM3 CapComp dc.l {$82000000+NonHandledInterrupt} ; Reserved dc.l {$82000000+NonHandledInterrupt} ; Reserved dc.l {$82000000+NonHandledInterrupt} ; irq19 I2C dc.l {$82000000+NonHandledInterrupt} ; irq20 UART2 TX (unbenutzt) dc.l {$82000000+UartRxInterrupt} ; irq21 UART2 RX (UART Com) dc.l {$82000000+NonHandledInterrupt} ; irq22 ADC end of conversion dc.l {$82000000+NonHandledInterrupt} ; irq23 TIM4 update dc.l {$82000000+NonHandledInterrupt} ; irq24 Flash
|
Der Sende-Puffer ist als Ring-Puffer mit 64 Byte aufgebaut. Die beiden Pointer UTX_WRT und UTX_RD zeigen jeweils auf die nächste „volle“ (RD) bzw. „leere“ (WRT) Speicherzelle.
Die Sub-Routine „Send-Char“ trägt das in der Variablen „TXCHAR“ übergebene Zeichen in den Ring-Puffer an der Stelle ein auf die UTX_WRT zeigt. Die Sub-Routine wird von der Applikation bei Bedarf direkt aufgerufen.
Die Sub-Routine „TX_UART“ wird von der Applikation bzw. von einem „Scheduler“ zyklisch aufgerufen und überträgt alle im Ring-Puffer vorhandenen Zeichen in einem Durchgang.

Der Empfangs-Puffer ist ebenfalls als Ring-Puffer mit 64 Byte aufgebaut. Die beiden Pointer URX_WRT und URX_RD zeigen jeweils auf die nächste „volle“ (RD) bzw. „leere“ (WRT) Speicherzelle.
Die Sub-Routine „Read_Char“ liest das nächste Zeichen (FIFO Struktur) auf welches der Pointer UTX-RD zeigt und legt dieses in der Variablen RXCHAR ab. Die Sub-Routine wird durch die Applikation aufgerufen um ein Zeichen vom Puffer abzuholen.
Die Interrup Sub-Routine „UartRxInterrupt.l“ (Long Pointer) wird durch den UART Receive Interrupt aufgerufen und legt das empfangene Zeichen an der Stelle im Ring-Puffer ab auf den der Pointer URX_WRT zeigt.

Copyright 2016 by Dipl.Ing.(FH) Franz Henkel
Dieses Dokument sowie dessen Inhalt, insbesondere Texte, Fotografien und Grafiken, unterliegt dem Copyright (© 2017) und sind nur mit einer schriftlicher Zustimmung des Autors, Dipl.Ing.(FH) Franz Henkel zur vollständigen oder auszugsweisen Weiterverwendung in Form einer gedruckten oder elektronischen Kopie oder Replikation bzw. einer vollständigen oder auszugsweisen Bereitstellung des Inhalts in schriftlicher, gedruckter oder elektronischer Form, zu verwenden.